<?php

declare(strict_types=1);

namespace core;

use PDO;
use PDOException;
use PDOStatement;
use Exception;
use Throwable;

/**
 * 数据库封装类
 *
 * 封装 PDO，提供更简洁、更安全的数据库操作方法
 * 并强制使用预处理语句 (Prepared Statements) 来防止 SQL 注入。
 */
class Database
{
    /** @var PDO PDO 连接实例 */
    private $pdo;

    /**
     * 构造函数，建立 PDO 连接
     *
     * @param array $conf 数据库配置数组
     * @throws Exception 如果配置缺失或连接失败
     */
    public function __construct(array $conf)
    {
        $driver = $conf['driver'] ?? 'mysql'; // 默认为 mysql
        $dsn = "{$driver}:host={$conf['host']};port={$conf['port']};dbname={$conf['database']};charset={$conf['charset']}";

        $options = [
            PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES   => false,
        ];

        try {
            $this->pdo = new PDO($dsn, $conf['username'], $conf['password'], $options);
        } catch (PDOException $e) {
            // 在真实应用中，不应暴露详细错误
            throw new Exception("Database connection failed: ");
        }
    }

    /**
     * 【核心】安全执行 SQL 并返回 PDOStatement
     * 这是所有其他方法的基础
     *
     * @param string $sql SQL 语句 (带 ? 或 :name 占位符)
     * @param array $params 要绑定的参数数组 (一维数组)
     * @return PDOStatement
     * @throws PDOException 如果 SQL 执行失败
     */
    public function query(string $sql, array $params = []): PDOStatement
    {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }

    /**
     * 辅助方法：获取所有行 (SELECT)
     *
     * @param string $sql
     * @param array $params
     * @return array (返回一个二维数组, 即使没有结果也返回空数组 [])
     */
    public function fetchAll(string $sql, array $params = []): array
    {
        return $this->query($sql, $params)->fetchAll();
    }

    /**
     * 辅助方法：获取单行 (SELECT)
     *
     * @param string $sql
     * @param array $params
     * @return array|false (返回关联数组或在没有匹配行时返回 false)
     */
    public function fetch(string $sql, array $params = []): array|false
    {
        return $this->query($sql, $params)->fetch();
    }

    /**
     * 辅助方法：执行 INSERT, UPDATE, DELETE
     * 返回受影响的行数
     *
     * @param string $sql
     * @param array $params
     * @return int (受影响的行数)
     */
    public function execute(string $sql, array $params = []): int
    {
        return $this->query($sql, $params)->rowCount();
    }

    /**
     * [C - Create] 插入一条记录
     *
     * @param string $table 表名
     * @param array $data 要插入的关联数组 [column => value]
     * @return string 最后插入记录的 ID
     */
    public function insert(string $table, array $data): string
    {
        if (empty($data)) {
            return '0';
        }

        // 构造字段名和命名占位符
        $keys = array_keys($data);
        // 例如：(username, email)
        $columns = implode(', ', $keys);
        // 例如：(:username, :email)
        $placeholders = ':' . implode(', :', $keys);

        $sql = "INSERT INTO {$table} ({$columns}) VALUES ({$placeholders})";

        // 执行
        $this->execute($sql, $data);

        return $this->lastInsertId();
    }

    /**
     * [U - Update] 更新记录
     *
     * @param string $table 表名
     * @param array $data 要更新的字段和值 [column => value]
     * @param string $where SQL WHERE 子句 (不包含 WHERE 关键字)
     * @param array $whereParams WHERE 子句中使用的参数数组
     * @return int 受影响的行数
     */
    public function update(string $table, array $data, string $where, array $whereParams = []): int
    {
        if (empty($data)) {
            return 0;
        }

        // 构造 SET 语句
        $setClauses = [];
        foreach (array_keys($data) as $key) {
            // 例如：username = :username
            $setClauses[] = "{$key} = :{$key}";
        }
        $set = implode(', ', $setClauses);

        $sql = "UPDATE {$table} SET {$set} WHERE {$where}";

        // 合并 SET 参数和 WHERE 参数
        $params = array_merge($data, $whereParams);

        // 执行
        return $this->execute($sql, $params);
    }

    /**
     * [D - Delete] 删除记录
     *
     * @param string $table 表名
     * @param string $where SQL WHERE 子句 (不包含 WHERE 关键字)
     * @param array $params WHERE 子句中使用的参数数组
     * @return int 受影响的行数
     */
    public function delete(string $table, string $where, array $params = []): int
    {
        $sql = "DELETE FROM {$table} WHERE {$where}";

        return $this->execute($sql, $params);
    }

    /**
     * 辅助方法：获取最后插入的 ID
     *
     * @return string (返回最后插入行的 ID)
     */
    public function lastInsertId(): string
    {
        return $this->pdo->lastInsertId();
    }

    /**
     * 启动一个事务
     *
     * @return bool
     */
    public function beginTransaction(): bool
    {
        return $this->pdo->beginTransaction();
    }

    /**
     * 提交一个事务
     *
     * @return bool
     */
    public function commit(): bool
    {
        return $this->pdo->commit();
    }

    /**
     * 回滚一个事务
     *
     * @return bool
     */
    public function rollBack(): bool
    {
        return $this->pdo->rollBack();
    }
}
